package de.mmis.core.base.abstracttree.serializer.internal;

import java.lang.reflect.InvocationTargetException;
import java.util.Map;

import de.mmis.core.base.DeserializableAnnotations.DeserializationMethod;
import de.mmis.core.base.abstracttree.InnerNode;
import de.mmis.core.base.abstracttree.LeafNode;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.abstracttree.serializer.DeserializableException;
import de.mmis.core.base.abstracttree.serializer.Serializer;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper;
import de.mmis.core.base.abstracttree.serializer.selector.InstanceSelector;
import de.mmis.core.base.abstracttree.serializer.selector.SimpleSelector;
import de.mmis.core.base.gclass.GClass;

/**
 * Serializer class for (de)serializing {@link Throwable}s from/to {@link Tree}s
 * 
 * @author Christian Koch
 */
public class ThrowableSerializer implements Serializer<Throwable> {

	@SuppressWarnings("unchecked")
	@DeserializationMethod
	public static Object deserialize(Tree[] exps, ClassLoader classLoader,
			String id, Map<String, Object> idmap)
			throws DeserializableException {

		if (exps.length != 4)
			throw new DeserializableException(new InnerNode(exps),
					"Throwable Tree has to be a 4-elem struct");

		Class<? extends Throwable> tclass = (Class<? extends Throwable>) SerializerHelper
				.deserializeObject(exps[0], GClass.fromClass(Class.class),
						classLoader, idmap);

		String message = exps[1].getAsString();
		StackTraceElement[] stackTrace = (StackTraceElement[]) SerializerHelper
				.deserializeObject(exps[2],
						GClass.fromClass(StackTraceElement[].class),
						classLoader, idmap);
		Throwable cause = (Throwable) SerializerHelper.deserializeObject(
				exps[3], GClass.fromClass(Throwable.class), classLoader, idmap);

		boolean initCause = false;

		Throwable throwable = initThrowable(tclass, new Class<?>[] {
				String.class, Throwable.class },
				new Object[] { message, cause });
		if (throwable != null)
			initCause = true;

		if (throwable == null) {
			throwable = initThrowable(tclass, new Class<?>[] { Throwable.class,
					String.class }, new Object[] { cause, message });
			if (throwable != null)
				initCause = true;
		}

		if (throwable == null)
			throwable = initThrowable(tclass, new Class<?>[] { String.class },
					new Object[] { message });

		if (throwable == null) {
			throwable = initThrowable(tclass,
					new Class<?>[] { Throwable.class }, new Object[] { cause });
			if (throwable != null)
				initCause = true;
		}

		if (throwable == null)
			throwable = initThrowable(tclass, new Class<?>[] {},
					new Object[] {});

		if (throwable == null) {
			throwable = new Throwable(tclass.getName()
					+ (message == null ? "" : " - " + message), cause);
			initCause = true;
		}

		// instantiate as early as possible to add idmap entry in time in case
		// it is needed in further method calls
		InstanceSelector ret = new SimpleSelector(throwable, id, idmap);

		if (!initCause)
			try {
				throwable.initCause(cause);
			} catch (IllegalArgumentException e) { // do nothing
			} catch (IllegalStateException e) { // do nothing
			}

		throwable.setStackTrace(stackTrace);

		return ret;
	}

	private static Throwable initThrowable(Class<? extends Throwable> tclass,
			Class<?>[] argtypes, Object[] args) {
		try {
			return tclass.getConstructor(argtypes).newInstance(args);
		} catch (SecurityException e) { // do nothing
		} catch (NoSuchMethodException e) { // do nothing
		} catch (IllegalArgumentException e) { // do nothing
		} catch (InstantiationException e) { // do nothing
		} catch (IllegalAccessException e) { // do nothing
		} catch (InvocationTargetException e) { // do nothing
		}
		return null;
	}

	@Override
	public Tree serialize(Throwable t, boolean explicit,
			Map<Object, String> refmap, ClassLoader classLoader) {

		InnerNode ret = new InnerNode(new LeafNode(
				SerializerHelper.typeIdentifierPrefix + "java-throwable"));

		ret.add(Tree.fromObject(t.getClass(), explicit, refmap, classLoader));
		ret.add(Tree.fromObject(t.getMessage(), explicit, refmap, classLoader));
		ret.add(Tree.fromObject(t.getStackTrace(), explicit, refmap,
				classLoader));
		ret.add(Tree.fromObject(t.getCause(), explicit, refmap, classLoader));

		return ret;
	}
}
